package com.liveinstars.im.core;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.liveinstars.api.dto.ImGetUserInfo;
import com.liveinstars.api.facade.ImUserCentreApi;
import com.liveinstars.im.common.enums.PassThroughMessageTypeEnum;
import com.liveinstars.im.core.service.ChatServerService;
import com.liveinstars.im.core.service.UserService;
import com.liveinstars.im.domain.dao.model.ChatServer;
import com.liveinstars.im.domain.dao.model.UserOnlineInfo;
import com.liveinstars.im.domain.manager.UserOnlineInfoManager;
import com.liveinstars.im.entity.Msg;
import com.onepiece.cache.service.CacheService;
import com.liveinstars.im.corebean.MsgHelper;
import com.liveinstars.im.corebean.PingMessage;
import com.liveinstars.im.corebean.ServerLoginMessage;
import com.liveinstars.im.corebean.TokenServer;
import com.onepiece.shipelves.common.utils.CacheUtil;
import com.onepiece.shipelves.common.utils.JsonUtil;
import io.github.bucket4j.Bucket;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 保持服务间通信通道
 *
 * @author ting
 *
 */
@Service
public class SessionManager {
	// 本地日志记录对象
	private static final Logger logger = LoggerFactory.getLogger(SessionManager.class);
	@Autowired
	private ChatServerService chatServerService;

	@Autowired
	private UserService userService;

	@Autowired
	private UserOnlineInfoManager userOnlineInfoManager;

	@Autowired
	private ImUserCentreApi imUserCentreApi;

//	@Autowired
//	private TokenApi tokenApi;

	@Autowired
	CacheService cache;

	/**
	 * 保存一对map是为了以空间换时间，让查找更迅速
	 */
	private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();

	private static Map<String, String> serverChannelIdMap = new ConcurrentHashMap<>();

	public static final Map<String, Bucket> LOCAL_CACHE = new ConcurrentHashMap<>();

	@Value("${netty.server.id}")
	public Long chatServerId;

	@Value("${netty.server.ip}")
	public String ip;

	@Value("${netty.server.outip}")
	public String outIp;

	@Value("${netty.server.port}")
	public Integer port;

	@Value("${netty.server.name}")
	public String chatServerName;

	public static void add(Session session) {
		if (session == null) {
			logger.error("session is null");
			throw new RuntimeException("session is null");
		}
		if (session.getChannel() == null) {
			logger.error("channel is null");
			throw new RuntimeException("session is null");
		}
		logger.info("channel id is {}", session.getChannel().id());
		sessionMap.put(session.getChannel().id().toString(), session);
		if (session.getType() == Session.SessionType.SS) {
			serverChannelIdMap.put(session.getServerIpPort(), session.getChannel().id().toString());
		}
	}

	public static Session get(String channelId) {
		return sessionMap.get(channelId);
	}

	public static String getServerChannelId(String serverIpPort) {
		return serverChannelIdMap.get(serverIpPort);
	}

	public synchronized void remove(String channelId) {
		logger.info("sessionmanger remove key:"+ channelId);
		Session session = sessionMap.get(channelId);
		if (session != null && session.getType().equals(Session.SessionType.SC)) {
			offlineUser(channelId);
		}
		sessionMap.remove(channelId);
		if (session.getType() == Session.SessionType.SS) {
			serverChannelIdMap.remove(session.getServerIpPort());
		}
	}

	/**
	 * 验证用户有效性，
	 * @param channelHandlerContext
	 * @param token
	 * @param userId
	 * @param clientId
	 * @param clientType
	 * @return
	 */
	public Session clientLoginAuth(ChannelHandlerContext channelHandlerContext,
								   String token, Long userId, String clientId, String clientType) {
		Session session = null;
		//去验证用户token和userid对应
		if(auth(token, userId, clientId, clientType)){
			String channelId = channelHandlerContext.channel().id().toString();
			session = new Session(clientId, channelHandlerContext.channel(), userId);
			SessionManager.add(session);
			//注册登录用户信息
			registerUser(clientId, channelId, clientType);
			return session;
		}else {
			return null;
		}
	}

	/**
	 * 调用远程接口验证用户有效性
	 * @param token
	 * @param userId
	 * @param clientId
	 * @param clientType
	 * @return
	 */
	private boolean auth(String token, Long userId, String clientId, String clientType) {
		logger.info("im auth token {} userid {}",token, userId);

		ImGetUserInfo imGetUserInfo = imUserCentreApi.validToken(token, userId);
		if(imGetUserInfo != null){
			logger.info("im auth success {} ",imGetUserInfo.getUserName());
			return true;
		}
		logger.info("im auth fail ");

		return false;
	}

	// 客户端认证检查
	public Session clientAuth(String channelId) {

		Session session = SessionManager.get(channelId);
		return session;
	}

	/**
	 * 处理收到的server login消息
	 *
	 * @param channelHandlerContext
	 * @return
	 */
	public boolean serverLogin(ChannelHandlerContext channelHandlerContext, ServerLoginMessage message) {
		String ip = message.getIp();
		int port = message.getPort();
		ChatServer server = chatServerService.getChatServerByIpAndPort(ip, port);
		if (server != null && server.getOnline()) {
			String serverId = ip + ":" + port;
			Session session = new Session(channelHandlerContext.channel(), serverId);
			SessionManager.add(session);

			logger.info("server(" + ip + "," + port + ") login success。");

			Msg.Message rtMessage = MsgHelper.newResultMessage(
					Msg.MessageType.LOGIN_SUCCESS, "认证成功！");
			session.send(rtMessage);
			logger.info("认证成功！");
			return true;
		} else {
			String msg;
			if (server == null) {
				msg = "server(" + ip + "," + port + ") login fail cant find。";
			} else if (server != null && !server.getOnline()) {
				msg = "server(" + ip + "," + port + ") login fail,not online。";
			} else {
				msg = "server(" + ip + "," + port + ") login fail,unknow reason。";
			}
			logger.info(msg);

			Msg.Message rtMessage = MsgHelper.newResultMessage(
					Msg.MessageType.LOGIN_ERROR, "服务器认证失败，重新认证!");

			logger.info("服务器认证失败，重新认证!！");
			channelHandlerContext.channel().writeAndFlush(rtMessage);
			return false;
		}
	}

	/**
	 * 当收到ping消息的时候响应pong消息
	 *
	 * @param channelHandlerContext
	 */
	public void serverPing(ChannelHandlerContext channelHandlerContext, PingMessage message) {
		Session session = SessionManager.get(channelHandlerContext.channel().id().toString());
		if (session != null) {
			Msg.Message pong = MsgHelper.newPingMessage(
					Msg.MessageType.SERVER_PONG, ip + ":" + port);
			session.send(pong);
		} else {
			logger.info("响应服务器ping时获取channel失败。");
		}
	}

	public void clientPing(Session session) {
		if (session != null) {
			Msg.Message pong = MsgHelper.newPingMessage(
					Msg.MessageType.CLIENT_PONG, ip + ":" + port);
			session.send(pong);
		} else {
			logger.info("响客户端ping时获取channel失败。");
		}
	}

	public void sendMessage(Msg.Message message, String channelId) {
		Session session = SessionManager.get(channelId);
		if (session != null) {
			session.send(message);
		}
	}

	/**
	 * 漫游发送（透传消息）
	 * @param toId
	 * @param passThroughMessageType
	 * @param message
	 * @return
	 */
	public boolean sendMessageRoaming(Long toId, int passThroughMessageType, Msg.Message message) {
		boolean reault = true;
		try {
			if (passThroughMessageType == PassThroughMessageTypeEnum.USER.getValue()) {
				// 将消息发送出去
				Set<TokenServer> tokenServerSet = getTokenServerByUserId(toId);
				logger.info("SessionManager sendMessageRoaming {} {}", toId, tokenServerSet);
				List<String> clientIdList = Lists.newArrayList();
				logger.info("SessionManager sendMessageRoaming");
				if (tokenServerSet != null) {
					//本机发送
					for (TokenServer server : tokenServerSet) {
						String clientId = server.getIp() + ":" + server.getPort();
						logger.info("SessionManager sendMessageRoaming {} {} {}" ,clientId, ip, port);
						if (clientId.equals(ip + ":" + port)){
							//本地
							Session session = SessionManager.get(server.getChannelId());
							if (session != null) {
								if (message.getChatMessage() != null) {
									logger.info("SessionManager sendMessageRoaming sendtomyserver message content:{} useravator:{} userGroupId:{}",
											message.getChatMessage().getContent(), message.getChatMessage().getUseravator(), message.getChatMessage().getUserGroupId());
								} else {
									logger.info("SessionManager sendMessageRoaming sendtomyserver message type: {}", message.getMessageType().getNumber());
								}
								session.send(message);
							}
						} else {
							//漫游
							logger.info("SessionManager sendMessageRoaming2 {} {} {}" ,clientId, ip, port);
							if(serverChannelIdMap.containsKey(clientId)) {
								if(clientIdList.indexOf(clientId) < 0) {
									String channelId = serverChannelIdMap.get(clientId);
									Msg.Message passThroughMessage = MsgHelper.newPassThroughMessage(toId, PassThroughMessageTypeEnum.USER.getValue(), message);
									logger.info("SessionManager sendMessageRoaming sendtoother server");
									// 获取本地存储的其他server的session
									Session serverSession = SessionManager.get(channelId);
									if (serverSession != null) {
										serverSession.send(passThroughMessage);
									}
								}
								clientIdList.add(clientId);
							}
						}
					}
				}
			}
		} catch (Exception e) {
			logger.error("sendMessageRoaming error toId:{}", toId, e);
			reault = false;
		}
		return reault;
	}

	/**
	 * 发送给本服务器用户
	 * @param message
	 * @return
	 */
	public boolean sendMessage(Long userId, Msg.Message message) {
		boolean r = true;
		// 将消息发送出去
		Set<TokenServer> tokenServerSet = getTokenServerByUserId(userId);
		Session session = null;
		if (tokenServerSet != null) {
			for (TokenServer tokenServer : tokenServerSet) {
				session = SessionManager.get(tokenServer.getChannelId());
				if (session != null) {
					logger.info("SessionManager sendMessage sendmy server");
					session.send(message);
				}
			}
		}

		return r;
	}

	/**
	 * 删除server表中记录，删除本服务器登陆的用户记录，防止其他服务器继续转发
	 */
	@PreDestroy
	public void destroy(){
		logger.info("SessionManager destroy begin");
		chatServerService.offline();
		logger.info("SessionManager destroy ok");
	}

	private void offlineUser(String channelId) {
		userOnlineInfoManager.deleteByChatServerIdAndChannelId(chatServerId, channelId);
		Session session = sessionMap.get(channelId);
		TokenServer tokenServer = new TokenServer(session.getClientId(), channelId, ip, port);
		cache.srem(CacheUtil.getImUserTokenChatserverList(session.getUserId()), JsonUtil.toJson(tokenServer));
	}

	private void registerUser(String clientId, String channelId, String clientType) {
		Session session = sessionMap.get(channelId);
		UserOnlineInfo userOnlineInfo = new UserOnlineInfo();
		userOnlineInfo.setUserId(session.getUserId());
		userOnlineInfo.setChannelId(channelId);
		userOnlineInfo.setChatServerId(chatServerId);
		userOnlineInfo.setClientId(channelId);
		userOnlineInfo.setClientType(clientType);
		userOnlineInfoManager.addOrUpdate(userOnlineInfo);
		//保存一份redis，用于快查
		TokenServer tokenServer = new TokenServer(clientId, userOnlineInfo.getChannelId(), ip, port);
		cache.sadd(CacheUtil.getImUserTokenChatserverList(userOnlineInfo.getUserId()), JsonUtil.toJson(tokenServer));
	}

	private Set<TokenServer> getTokenServerByUserId(Long toId) {
		Set<String> smembers = cache.smembers(CacheUtil.getImUserTokenChatserverList(toId));
		logger.info("SessionManager getTokenServerByUserId {}", smembers.size());
		Set<TokenServer> tokenServers = new HashSet<>();

		if(smembers != null && smembers.size() > 0) {
			for (String json : smembers) {

				tokenServers.add(JSON.parseObject(json, TokenServer.class));
			}
		}
		return tokenServers;
	}

	public static Map<String, Session> getSessionMap() {
		return sessionMap;
	}

	public static Map<String, String> getServerChannelIdMap() {
		return serverChannelIdMap;
	}

	public Long getChatServerId() {
		return chatServerId;
	}

	public void setChatServerId(Long chatServerId) {
		this.chatServerId = chatServerId;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getOutIp() {
		return outIp;
	}

	public void setOutIp(String outIp) {
		this.outIp = outIp;
	}

	public Integer getPort() {
		return port;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	public String getChatServerName() {
		return chatServerName;
	}

	public void setChatServerName(String chatServerName) {
		this.chatServerName = chatServerName;
	}
}